/*
 * This file is part of org.kalmeo.util.
 * 
 * org.kalmeo.util is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * org.kalmeo.util is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with org.kalmeo.util.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * Creation date : 21 nov. 2007
 * Copyright (c) Kalmeo 2007-2008. All rights reserved.
 * http://www.kalmeo.org
 */

package org.kalmeo.util.frame;

/**
 * @author bbeaulant
 */
public class FrameHandler {

	/**
	 * This class add to a {@link Frame} the linked list concept
	 */
	private class LinkedFrame {

		// Th associated Frame
		private final Frame frame;

		private LinkedFrame previous;
		private LinkedFrame next;

		/**
		 * Construct a {@link LinkedFrame}
		 * 
		 * @param frame the associated {@link Frame}
		 */
		public LinkedFrame(Frame frame) {
			this.frame = frame;
		}

	}

	// The top linkedFrame
	private LinkedFrame topLinkedFrame;

	/**
	 * Returns {@link LinkedFrame} associated withe the given <code>frame</code>,
	 * or null if the <code>frame</code> is not in the stack.
	 * 
	 * @param frame
	 * @return The {@link LinkedFrame} associated withe the given
	 *         <code>frame</code>, or null if the <code>frame</code> is not
	 *         in the stack
	 */
	private LinkedFrame getLinkedFrame(Frame frame) {
		for (LinkedFrame linkedFrame = topLinkedFrame; linkedFrame != null; linkedFrame = linkedFrame.previous) {
			if (linkedFrame.frame == frame) {
				return linkedFrame;
			}
		}
		return null;
	}

	/**
	 * Returns th {@link Frame} on top of the frames stack, or null if the
	 * {@link FrameHandler} is empty.
	 * 
	 * @return the {@link Frame} on top of the stack
	 */
	public Frame getTopFrame() {
		if (topLinkedFrame != null) {
			return topLinkedFrame.frame;
		}
		return null;
	}

	/**
	 * Push a frame on top of the frames stack
	 * 
	 * @param frame the {@link Frame} to push
	 * @return <code>true</code> if frame is pushed or <code>false</code> if
	 *         the frame was already present in to the stack
	 */
	public boolean pushFrame(Frame frame) {
		if (getLinkedFrame(frame) == null) {
			LinkedFrame linkedFrame = new LinkedFrame(frame);
			linkedFrame.previous = topLinkedFrame;
			if (topLinkedFrame != null) {
				topLinkedFrame.next = linkedFrame;
			}
			topLinkedFrame = linkedFrame;
			frame.onAdded();
			return true;
		}
		return false;
	}
	
	/**
	 * Remove a frame ont top of the frames stack
	 * 
	 * @param frame the {@link Frame} to remove
	 */
	public void removeFrame(Frame frame) {
		LinkedFrame linkedFrame = getLinkedFrame(frame);
		if (linkedFrame != null) {
			if (linkedFrame == topLinkedFrame) {
				topLinkedFrame = linkedFrame.previous;
			}
			if (linkedFrame.previous != null) {
				linkedFrame.previous.next = linkedFrame.next;
			}
			if (linkedFrame.next != null) {
				linkedFrame.next.previous = linkedFrame.previous;
			}
			processLinkedFrameRemoveActions(linkedFrame);
		}
	}

	/**
	 * Remove all frames
	 */
	public void removeAllFrames() {
		for (LinkedFrame linkedFrame = topLinkedFrame; linkedFrame != null;) {
			LinkedFrame previousLinkedFrame = linkedFrame.previous;
			processLinkedFrameRemoveActions(linkedFrame);
			linkedFrame = previousLinkedFrame;
		}
		topLinkedFrame = null;
	}

	/**
	 * Remove all frames on top of the given <code>frame</code>. The
	 * <code>frame</code> is not removed and become the new top frame.
	 * 
	 * @param frame the reference {@link Frame}
	 */
	public void removeAllFrameOnTopOf(Frame frame) {
		LinkedFrame newToplinkedFrame = getLinkedFrame(frame);
		if (newToplinkedFrame != null) {
			topLinkedFrame = newToplinkedFrame;
			for (LinkedFrame linkedFrame = newToplinkedFrame.next; linkedFrame != null;) {
				LinkedFrame nextLinkedFrame = linkedFrame.next;
				processLinkedFrameRemoveActions(linkedFrame);
				linkedFrame = nextLinkedFrame;
			}
			topLinkedFrame.next = null;
		}
	}

	/**
	 * Process a message
	 * 
	 * @param identifier the message identifier
	 * @param arguments the message arguments
	 * @return <code>true</code> if the message has been processed by a frame.
	 */
	public boolean processMessage(Object identifier, Object[] arguments) {
		for (LinkedFrame linkedFrame = topLinkedFrame; linkedFrame != null; linkedFrame = linkedFrame.previous) {
			if (!linkedFrame.frame.onMessage(identifier, arguments)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Helper to process frame remove actions
	 * 
	 * @param linkedFrame
	 */
	private void processLinkedFrameRemoveActions(LinkedFrame linkedFrame) {
		linkedFrame.previous = null;
		linkedFrame.next = null;
		linkedFrame.frame.onRemoved();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer("framesHandler :");
		for (LinkedFrame linkedFrame = topLinkedFrame; linkedFrame != null; linkedFrame = linkedFrame.previous) {
			String className = linkedFrame.frame.getClass().getName();
			buffer.append("\n > ").append(className.substring(className.lastIndexOf('.') + 1));
		}
		return buffer.toString();
	}

}
